enum step {
  start = 'start',
  rule = 'rule',
  action = 'action',
  end = 'end',
  empty = 'empty',
}

const stepsData: Array<Step> = [
  {
    type: step.start,
    id: 1,
    next: 2,
    yes: null,
    no: null,
  },
  {
    type: step.rule,
    id: 2,
    next: null,
    yes: 3,
    no: 4,
  },
  {
    type: step.action,
    id: 3,
    next: 9,
    yes: null,
    no: null,
  },
  {
    type: step.rule,
    id: 9,
    next: null,
    yes: 10,
    no: 5,
  },
  {
    type: step.action,
    id: 10,
    next: 5,
    yes: null,
    no: null,
  },
  {
    type: step.action,
    id: 4,
    next: 6,
    yes: null,
    no: null,
  },
  {
    type: step.action,
    id: 6,
    next: 7,
    yes: null,
    no: null,
  },
  {
    type: step.rule,
    id: 7,
    next: null,
    yes: 5,
    no: 8,
  },
  {
    type: step.end,
    id: 8,
    next: null,
    yes: null,
    no: null,
  },
  {
    type: step.end,
    id: 5,
    next: null,
    yes: null,
    no: null,
  },
]

const stepInterval = 120
export const stepWidthDefault = 150
const stepNodeWidthHalf = 25
const stepNodeWidth = stepNodeWidthHalf * 2

const getYesBranchCount = (step: Step) => {
  let count = 0
  let current = step.noStep
  while ((current?.nextStep || current?.yesStep) && current.parentStep !== step.parentStep) {
    if (current.yesStep) {
      count++
      current = current.yesStep
    } else if (current.nextStep) {
      current = current.nextStep
    } else {
      break
    }
  }
  return count
}

const getNoBranchCount = (step: Step) => {
  let count = 0
  let current = step.yesStep
  while ((current?.nextStep || current?.noStep) && current.parentStep !== step.parentStep) {
    if (current.noStep) {
      count++
      current = current.noStep
    } else if (current.nextStep) {
      current = current.nextStep
    } else {
      break
    }
  }
  return count
}

const markStepPosition = (step: Step, level: number) => {
  step.level = Math.max(level, step.level || 0)
  step.top = step.level * stepInterval
  if (step.type === 'start') {
    step.left = -stepNodeWidthHalf
  }

  if (step.type === 'rule') {
    step.width = (Math.floor((getNoBranchCount(step) + getYesBranchCount(step)) / 2) + 1) * stepWidthDefault
  }

  if (step.prevStep) {
    if (step.prevStep.yes === step.id) {
      step.left = step.prevStep.left! + step.prevStep.width!
    } else if (step.prevStep.no === step.id) {
      step.left = step.prevStep.left! - step.prevStep.width!
    } else if (step.prevStep.next === step.id) {
      step.left = step.prevStep.left
    } else if (step.type === 'empty') {
      step.left = step.prevStep.left
    } else {
      step.left = step.prevStep.left
    }
  }

  step.nextStep && markStepPosition(step.nextStep, step.level + 1)
  step.yesStep && markStepPosition(step.yesStep, step.level + 1)
  step.noStep && markStepPosition(step.noStep, step.level + 1)
}
//
// const linkEmptyStep = (step: Step, target: Step, emptyStep: Step) => {
//   step.nextStep.id === target.id &&
// }

const getNoEndRuleStep = (step: Step): Step | undefined => {
  if (step.parentStep?.endStep) {
    return getNoEndRuleStep(step.parentStep)
  }
  return step.parentStep
}

const generateEmptySteps = (steps: Array<Step>, stepMap: StepMap) => {
  const emptySteps: Array<Step> = []

  steps.forEach((step) => {
    if (step.prevSteps?.length === 1) {
      step.prevStep = step.prevSteps[0]
    } else if (step.prevSteps && step.prevSteps.length > 1) {
      const map: {
        [key: number]: Array<Step>
      } = {}
      for (let i = 0; i < step.prevSteps.length; i++) {
        const prevStep = step.prevSteps[i]

        if (prevStep.type !== 'rule') {
          if (prevStep.parentStep?.endStep) {
            const noEndRuleStep = getNoEndRuleStep(prevStep.parentStep)
            if (noEndRuleStep) {
              map[noEndRuleStep.id] = map[noEndRuleStep.id] || []
              map[noEndRuleStep.id].push(prevStep)
            }
          } else {
            if (prevStep.parentStep) {
              map[prevStep.parentStep.id] = map[prevStep.parentStep.id] || []
              map[prevStep.parentStep.id].push(prevStep)
            }
          }
        } else {
          if (prevStep.endStep) {
            const noEndRuleStep = getNoEndRuleStep(prevStep)
            if (noEndRuleStep) {
              map[noEndRuleStep.id] = map[noEndRuleStep.id] || []
              map[noEndRuleStep.id].push(prevStep)
            }
          } else {
            map[prevStep.id] = map[prevStep.id] || []
            map[prevStep.id].push(prevStep)
          }
        }
      }

      while (Object.keys(map).length) {
        for (let key in map) {
          if (map[key].length === 2) {
            const [step1, step2] = map[key]
            const emptyStep: Step = {
              type: 'empty',
              id: -1,
              next: null,
              yes: null,
              no: null,
            }

            if (step1.id === step2.id && step1.type === 'rule') {
              step1.yesStep = emptyStep
              step1.noStep = emptyStep

              emptyStep.prevStep = step1
              emptyStep.parentStep = step1.parentStep
            } else if (step1.id === step2.id && step1.type === 'empty') {
            } else {
              if (step1.type === 'empty') {
                step1.nextStep === emptyStep
              } else {
                if (step1.yes === step.id) {
                  step1.yesStep = emptyStep
                }
                if (step1.no === step.id) {
                  step1.noStep = emptyStep
                }
                if (step1.next === step.id) {
                  step1.nextStep = emptyStep
                }
              }
              if (step2.type === 'empty') {
                step2.nextStep = emptyStep
              } else {
                if (step2.yes === step.id) {
                  step2.yesStep = emptyStep
                }
                if (step2.no === step.id) {
                  step2.noStep = emptyStep
                }
                if (step2.next === step.id) {
                  step2.nextStep = emptyStep
                }
              }

              if (step1.type === 'rule') {
                if (step1.endStep) {
                  const noEndRuleStep = getNoEndRuleStep(step1)
                  emptyStep.prevStep = noEndRuleStep
                  emptyStep.parentStep = noEndRuleStep?.parentStep
                } else {
                  emptyStep.prevStep = step1
                  emptyStep.parentStep = step1.parentStep
                }
              } else {
                if (step1.parentStep?.endStep) {
                  const noEndRuleStep = getNoEndRuleStep(step1.parentStep)
                  emptyStep.prevStep = noEndRuleStep
                  emptyStep.parentStep = noEndRuleStep?.parentStep
                } else {
                  emptyStep.prevStep = step1.parentStep
                  emptyStep.parentStep = step1.parentStep?.parentStep
                }
              }
            }

            emptyStep.nextStep = step
            step.noBranch = false
            emptySteps.push(emptyStep)

            if (step1.type !== 'empty') {
              const idx1 = step.prevSteps.findIndex((ps) => ps.id === step1.id)
              step.prevSteps.splice(idx1, 1)
            }
            if (step2.type !== 'empty') {
              const idx2 = step.prevSteps.findIndex((ps) => ps.id === step2.id)
              step.prevSteps.splice(idx2, 1)
            }

            delete map[key]

            if (emptyStep.parentStep) {
              map[emptyStep.parentStep.id] = map[emptyStep.parentStep.id] || []
              map[emptyStep.parentStep.id].push(emptyStep)
            }

            if (Object.keys(map).length === 0) {
              step.prevStep = emptyStep
            }
          }
        }
      }
    }
  })

  return emptySteps
}

const link = (step: Step, next: 'next' | 'yes' | 'no', stepMap: StepMap) => {
  const target = step[next]
  if (target) {
    if (next !== 'next') {
      stepMap[target].parentStep = step
    }
    if (step.parentStep) {
      stepMap[target].parentStep = step.type === 'rule' ? step : step.parentStep
      if (stepMap[target].type === 'end') {
        if (!stepMap[target].parentStep!.endStep) {
          stepMap[target].parentStep!.endStep = stepMap[target]
        } else if (stepMap[target].parentStep?.endStep?.id === stepMap[target].id) {
          stepMap[target].parentStep!.endStep = undefined
        }
        let current = stepMap[target].parentStep
        while (current?.parentStep) {
          if (current.parentStep.endStep?.id === stepMap[target].id) {
            current.parentStep.endStep = undefined
          }
          current = current.parentStep
        }
      }
    }
    if (step.noBranch) {
      stepMap[target].noBranch = true
    }
    switch (next) {
      case 'next':
        stepMap[step.id].nextStep = stepMap[target]
        break
      case 'no':
        stepMap[step.id].noStep = stepMap[target]
        stepMap[target].noBranch = true
        break
      case 'yes':
        stepMap[step.id].yesStep = stepMap[target]
        break
    }
    stepMap[target].prevSteps = stepMap[target].prevSteps || []
    stepMap[target].prevSteps!.push(step)
  }
}

const linkPath = (start: [number, number], end: [number, number], type: string, branch: string = 'next', width?: number) => {
  const offset = stepNodeWidthHalf
  const radius = 15
  let [startX, startY] = start
  let [endX, endY] = end

  startX += offset
  startY += offset
  endX += offset
  endY += offset

  if (startX === endX) {
    if (type === 'rule') {
      if (branch === 'yes') {
        return `M${startX},${startY} L${endX + stepWidthDefault - radius},${startY} Q${endX + stepWidthDefault},${startY} ${endX + stepWidthDefault},${startY + radius} L${
          endX + stepWidthDefault
        },${endY - radius} Q${endX + stepWidthDefault},${endY} ${endX + stepWidthDefault - radius},${endY} L${endX + stepWidthDefault - radius},${endY} ${endX},${endY}`
      }
      if (branch === 'no') {
        return `M${startX},${startY} L${endX - stepWidthDefault + radius},${startY} Q${endX - stepWidthDefault},${startY} ${endX - stepWidthDefault},${startY + radius} L${
          endX - stepWidthDefault
        },${endY - radius} Q${endX - stepWidthDefault},${endY} ${endX - stepWidthDefault + radius},${endY} L${endX - stepWidthDefault + radius},${endY} ${endX},${endY}`
      }
      return ''
    } else {
      return `M${startX},${startY} L${endX},${endY}`
    }
  } else if (startX > endX) {
    if (type === 'rule') {
      if (width && startX - endX > width) {
        return `M${startX},${startY} L${startX - width + radius},${startY} Q${startX - width},${startY} ${startX - width},${startY + radius} L${startX - width},${endY - radius} Q${
          startX - width
        },${endY} ${startX - width - radius},${endY} L${startX - width - radius},${endY} ${endX},${endY}`
      } else {
        return `M${startX},${startY} L${endX + radius},${startY} Q${endX},${startY} ${endX},${startY + radius} L${endX},${endY}`
      }
    } else {
      return `M${startX},${startY} L${startX},${endY - radius} Q${startX},${endY} ${startX - radius},${endY}  L${endX},${endY}`
    }
  } else {
    if (type === 'rule') {
      if (width && endX - startX > width) {
        return `M${startX},${startY} L${startX + width - radius},${startY} Q${startX + width},${startY} ${startX + width},${startY + radius} L${startX + width},${endY - radius} Q${
          startX + width
        },${endY} ${startX + width + radius},${endY} L${startX + width + radius},${endY} ${endX},${endY}`
      } else {
        return `M${startX},${startY} L${endX - radius},${startY} Q${endX},${startY} ${endX},${startY + radius} L${endX},${endY}`
      }
    } else {
      return `M${startX},${startY} L${startX},${endY - radius} Q${startX},${endY} ${startX + radius},${endY} L${endX},${endY}`
    }
  }
}

const generateLinkPath = (steps: Array<Step>) => {
  const paths: Array<Path> = []
  steps.forEach(({ left, top, type, nextStep, yesStep, noStep, width }) => {
    if (nextStep) {
      const path = linkPath([left!, top!], [nextStep.left!, nextStep.top!], type, 'next')
      paths.push({
        path,
        dash: nextStep.noBranch!,
      })
    }
    if (yesStep) {
      const path = linkPath([left!, top!], [yesStep.left!, yesStep.top!], type, 'yes', width)
      paths.push({
        path,
        dash: false,
      })
    }
    if (noStep) {
      const path = linkPath([left!, top!], [noStep.left!, noStep.top!], type, 'no', width)
      paths.push({
        path,
        dash: true,
      })
    }
  })
  return paths
}

export const generateSteps = (steps = stepsData) => {
  const stepMap: StepMap = {}
  let root = null

  steps.forEach((step) => {
    stepMap[step.id] = step
    if (step.type === 'start') {
      root = step
    }
  })

  steps.forEach((step) => {
    link(step, 'next', stepMap)
    link(step, 'yes', stepMap)
    link(step, 'no', stepMap)
  })

  console.log('steps', steps)

  const emptySteps = generateEmptySteps(steps, stepMap)

  root && markStepPosition(root, 0)

  console.log('emptySteps', emptySteps)

  const stepsData = [...steps, ...emptySteps]
  const linksData = generateLinkPath(stepsData)
  return [stepsData, linksData]
}
